/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../../toolkit/xml.h"
#include "../../toolkit/keyboard.h"
#include "../misc.h"
#include "../geom.h"
#include "../frame.h"
#include "../actions.h"
#include "../screen.h"
#include "../window.h"
#include "../stacking.h"
#include "../client.h"
enum switch_type {
	LAST,
	CURRENT,
	RELATIVE,
	ABSOLUTE
};
struct options {
	enum switch_type type;
	union {
		struct {
			guint desktop;
		} abs;
		struct {
			gboolean linear;
			gboolean wrap;
			enum wm_direction dir;
		} rel;
	} u;
	gboolean send;
	gboolean follow;
	gboolean interactive;
};

static gboolean i_input_func(guint initial_state, XEvent *e, struct t_ic *ic, gpointer options, gboolean *used)
{
	guint mods;
	(void)ic;
	(void)options;
	(void)used;
	mods = t_keyboard_only_modmasks(e->xkey.state);
	if(e->type == KeyRelease) {
		mods &= ~t_keyboard_keyevent_to_modmask(e);
	}
	if(e->type == KeyPress) {
		KeySym sym = t_keyboard_keypress_to_keysym(e);
		if(sym == XK_Escape)
			return FALSE;
		else if((sym == XK_Return || sym == XK_KP_Enter) && !initial_state)
			return FALSE;
	} else if(e->type == KeyRelease && initial_state && !(mods & initial_state)) {
		return FALSE;
	}
	return TRUE;
}

static gboolean i_pre_func(guint initial_state, gpointer options)
{
	if(!initial_state) {
		struct options *o = options;
		o->interactive = FALSE;
		return FALSE;
	} else {
		return TRUE;
	}
}

static void i_post_func(gpointer options)
{
	(void)options;
}


static gpointer setup_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	xmlNodePtr n;
	struct options *o;
	(void)pre;
	(void)input;
	(void)cancel;
	(void)post;
	o = g_slice_new0(struct options);
	o->type = ABSOLUTE;
	o->u.abs.desktop = screen_desktop;
	o->u.rel.wrap = TRUE;
	if((n = t_xml_find_node(node, "to"))) {
		gchar *s = t_xml_node_string(n);
		if(!g_ascii_strcasecmp(s, "last"))
			o->type = LAST;
		else if(!g_ascii_strcasecmp(s, "current"))
			o->type = CURRENT;
		else if(!g_ascii_strcasecmp(s, "next")) {
			o->type = RELATIVE;
			o->u.rel.linear = TRUE;
			o->u.rel.dir = WM_DIRECTION_EAST;
		} else if(!g_ascii_strcasecmp(s, "previous")) {
			o->type = RELATIVE;
			o->u.rel.linear = TRUE;
			o->u.rel.dir = WM_DIRECTION_WEST;
		} else if(!g_ascii_strcasecmp(s, "north") || !g_ascii_strcasecmp(s, "up")) {
			o->type = RELATIVE;
			o->u.rel.dir = WM_DIRECTION_NORTH;
		} else if(!g_ascii_strcasecmp(s, "south") || !g_ascii_strcasecmp(s, "down")) {
			o->type = RELATIVE;
			o->u.rel.dir = WM_DIRECTION_SOUTH;
		} else if(!g_ascii_strcasecmp(s, "west") || !g_ascii_strcasecmp(s, "left")) {
			o->type = RELATIVE;
			o->u.rel.dir = WM_DIRECTION_WEST;
		} else if(!g_ascii_strcasecmp(s, "east") || !g_ascii_strcasecmp(s, "right")) {
			o->type = RELATIVE;
			o->u.rel.dir = WM_DIRECTION_EAST;
		} else {
			o->type = ABSOLUTE;
			o->u.abs.desktop = atoi(s) - 1;
		}
		g_free(s);
	}
	if((n = t_xml_find_node(node, "wrap")))
		o->u.rel.wrap = t_xml_node_bool(n);
	return o;
}

static gpointer setup_go_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o;
	o = setup_func(node, pre, input, cancel, post);
	if(o->type == RELATIVE) {
		o->interactive = TRUE;
		*pre = i_pre_func;
		*input = i_input_func;
		*post = i_post_func;
	}
	return o;
}

static gpointer setup_send_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	xmlNodePtr n;
	struct options *o;
	o = setup_func(node, pre, input, cancel, post);
	if((n = t_xml_find_node(node, "desktop"))) {
		o->u.abs.desktop = t_xml_node_int(n) - 1;
		o->type = ABSOLUTE;
	}
	o->send = TRUE;
	o->follow = TRUE;
	if((n = t_xml_find_node(node, "follow")))
		o->follow = t_xml_node_bool(n);
	if(o->type == RELATIVE && o->follow) {
		o->interactive = TRUE;
		*pre = i_pre_func;
		*input = i_input_func;
		*post = i_post_func;
	}
	return o;
}

static void free_func(gpointer o)
{
	g_slice_free(struct options, o);
}

static gboolean run_func(struct wm_actions_data *data, gpointer options)
{
	struct options *o = options;
	guint d;
	switch (o->type) {
	case LAST:
		d = screen_last_desktop;
		break;
	case CURRENT:
		d = screen_desktop;
		break;
	case ABSOLUTE:
		d = o->u.abs.desktop;
		break;
	case RELATIVE:
		d = screen_find_desktop(screen_desktop, o->u.rel.dir, o->u.rel.wrap, o->u.rel.linear);
		break;
	default:
		g_assert_not_reached();
	}
	if(d < screen_num_desktops && (d != screen_desktop || (data->client && data->client->desktop != screen_desktop))) {
		gboolean go = TRUE;
		actions_client_move(data, TRUE);
		if(o->send && data->client && client_normal(data->client)) {
			client_set_desktop(data->client, d, o->follow, FALSE);
			go = o->follow;
		}
		if(go) {
			screen_set_desktop(d, TRUE);
			if(data->client)
				client_bring_helper_windows(data->client);
		}
		actions_client_move(data, FALSE);
	}
	return o->interactive;
}

static gpointer setup_follow(xmlNodePtr node)
{
	xmlNodePtr n;
	struct options *o = g_slice_new0(struct options);
	o->send = TRUE;
	o->follow = TRUE;
	if((n = t_xml_find_node(node, "follow")))
		o->follow = t_xml_node_bool(n);
	return o;
}

static gpointer setup_go_last_func(xmlNodePtr node)
{
	struct options *o = g_slice_new0(struct options);
	(void)node;
	o->type = LAST;
	return o;
}

static gpointer setup_send_last_func(xmlNodePtr node)
{
	struct options *o = setup_follow(node);
	o->type = LAST;
	return o;
}

static gpointer setup_go_abs_func(xmlNodePtr node)
{
	xmlNodePtr n;
	struct options *o = g_slice_new0(struct options);
	o->type = ABSOLUTE;
	if((n = t_xml_find_node(node, "desktop")))
		o->u.abs.desktop = t_xml_node_int(n) - 1;
	else
		o->u.abs.desktop = screen_desktop;
	return o;
}

static void setup_rel(struct options *o, xmlNodePtr node, gboolean lin, enum wm_direction dir,
	wm_actions_i_pre_func *pre, wm_actions_i_input_func *input, wm_actions_i_post_func *post)
{
	xmlNodePtr n;
	o->type = RELATIVE;
	o->u.rel.linear = lin;
	o->u.rel.dir = dir;
	o->u.rel.wrap = TRUE;
	if((n = t_xml_find_node(node, "wrap")))
		o->u.rel.wrap = t_xml_node_bool(n);
	if(input) {
		o->interactive = TRUE;
		*pre = i_pre_func;
		*input = i_input_func;
		*post = i_post_func;
	}
}

static gpointer setup_go_next_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = g_slice_new0(struct options);
	(void)cancel;
	setup_rel(o, node, TRUE, WM_DIRECTION_EAST, pre, input, post);
	return o;
}

static gpointer setup_send_next_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_follow(node);
	(void)cancel;
	setup_rel(o, node, TRUE, WM_DIRECTION_EAST, pre, (o->follow ? input : NULL), post);
	return o;
}

static gpointer setup_go_prev_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = g_slice_new0(struct options);
	(void)cancel;
	setup_rel(o, node, TRUE, WM_DIRECTION_WEST, pre, input, post);
	return o;
}

static gpointer setup_send_prev_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_follow(node);
	(void)cancel;
	setup_rel(o, node, TRUE, WM_DIRECTION_WEST, pre, (o->follow ? input : NULL), post);
	return o;
}

static gpointer setup_go_left_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = g_slice_new0(struct options);
	(void)cancel;
	setup_rel(o, node, FALSE, WM_DIRECTION_WEST, pre, input, post);
	return o;
}

static gpointer setup_send_left_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_follow(node);
	(void)cancel;
	setup_rel(o, node, FALSE, WM_DIRECTION_WEST, pre, (o->follow ? input : NULL), post);
	return o;
}

static gpointer setup_go_right_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = g_slice_new0(struct options);
	(void)cancel;
	setup_rel(o, node, FALSE, WM_DIRECTION_EAST, pre, input, post);
	return o;
}

static gpointer setup_send_right_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_follow(node);
	(void)cancel;
	setup_rel(o, node, FALSE, WM_DIRECTION_EAST, pre, (o->follow ? input : NULL), post);
	return o;
}

static gpointer setup_go_up_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = g_slice_new0(struct options);
	(void)cancel;
	setup_rel(o, node, FALSE, WM_DIRECTION_NORTH, pre, input, post);
	return o;
}

static gpointer setup_send_up_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_follow(node);
	(void)cancel;
	setup_rel(o, node, FALSE, WM_DIRECTION_NORTH, pre, (o->follow ? input : NULL), post);
	return o;
}

static gpointer setup_go_down_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = g_slice_new0(struct options);
	(void)cancel;
	setup_rel(o, node, FALSE, WM_DIRECTION_SOUTH, pre, input, post);
	return o;
}

static gpointer setup_send_down_func(xmlNodePtr node, wm_actions_i_pre_func *pre, wm_actions_i_input_func *input,
	wm_actions_i_cancel_func *cancel, wm_actions_i_post_func *post)
{
	struct options *o = setup_follow(node);
	(void)cancel;
	setup_rel(o, node, FALSE, WM_DIRECTION_SOUTH, pre, (o->follow ? input : NULL), post);
	return o;
}

void action_desktop_startup(void)
{
	actions_register_i("GoToDesktop", setup_go_func, free_func, run_func);
	actions_register_i("SendToDesktop", setup_send_func, free_func, run_func);
	actions_register("DesktopLast", setup_go_last_func, free_func, run_func);
	actions_register("SendToDesktopLast", setup_send_last_func, free_func, run_func);
	actions_register("Desktop", setup_go_abs_func, free_func, run_func);
	actions_register_i("DesktopNext", setup_go_next_func, free_func, run_func);
	actions_register_i("SendToDesktopNext", setup_send_next_func, free_func, run_func);
	actions_register_i("DesktopPrevious", setup_go_prev_func, free_func, run_func);
	actions_register_i("SendToDesktopPrevious", setup_send_prev_func, free_func, run_func);
	actions_register_i("DesktopLeft", setup_go_left_func, free_func, run_func);
	actions_register_i("SendToDesktopLeft", setup_send_left_func, free_func, run_func);
	actions_register_i("DesktopRight", setup_go_right_func, free_func, run_func);
	actions_register_i("SendToDesktopRight", setup_send_right_func, free_func, run_func);
	actions_register_i("DesktopUp", setup_go_up_func, free_func, run_func);
	actions_register_i("SendToDesktopUp", setup_send_up_func, free_func, run_func);
	actions_register_i("DesktopDown", setup_go_down_func, free_func, run_func);
	actions_register_i("SendToDesktopDown", setup_send_down_func, free_func, run_func);
}
